﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;

namespace Shapes.Geometry.Drawings
{
    /// <summary>
    /// A spline part consisting of three points describing a curve.
    /// </summary>
    public class SplineSegment : Drawing
    {
        SplinePoint startPoint, endPoint, handlerPoint;
        uint aproximationVertexCount;

        /// <summary>
        /// The Point which defines the start of the segment.
        /// </summary>
        public SplinePoint StartPoint { get { return startPoint; } set { startPoint = value; OnChange(); } }

        /// <summary>
        /// The Point which defines the end og the segment.
        /// </summary>
        public SplinePoint EndPoint { get { return endPoint; } set { endPoint = value; OnChange(); } }

        /// <summary>
        /// The Point which defines the handler which the curve does not cross.
        /// </summary>
        public SplinePoint HandlerPoint { get { return handlerPoint; } set { handlerPoint = value; OnChange(); } }

        /// <summary>
        /// The Position of the Start of the segment (can be different to StartPoint, if it is also a handler).
        /// </summary>
        public Vector2 StartPosition
        {
            get
            {
                if (startPoint.IsHandler)
                    return startPoint.Position + 0.5f * (handlerPoint.Position - startPoint.Position);
                else
                    return startPoint.Position;
            }
        }

        /// <summary>
        /// The Position of the End of the segment (can be different to EndPoint, if it is also a handler).
        /// </summary>
        public Vector2 EndPosition
        {
            get
            {
                if (endPoint.IsHandler)
                    return endPoint.Position - 0.5f * (endPoint.Position - handlerPoint.Position);
                else
                    return endPoint.Position;
            }
        }

        /// <summary>
        /// A LineStrip which is an aproximation to the segment. It is needed to evaluate some things like the BorderLength (not accurate!).
        /// </summary>
        public LineStrip LineStripAproximation { get; private set; }

        /// <summary>
        /// The number of vertices used to generate the LineStripAproximation. If a new value is set here, a new LineStrip is created.
        /// </summary>
        public uint AproximationVertexCount { get { return aproximationVertexCount; } set { aproximationVertexCount = value; MakeAproximation(); } }


        /// <summary>
        /// Creates a new SplineSegment instance
        /// </summary>
        /// <param name="startPosition">the position of the start point</param>
        /// <param name="handlerPosition">the position of the handler point which is never crossed</param>
        /// <param name="endPosition">the position of the end point</param>
        public SplineSegment(Vector2 startPosition, Vector2 handlerPosition, Vector2 endPosition)
            : this(
            new SplinePoint(startPosition, false),
            new SplinePoint(handlerPosition, true),
            new SplinePoint(endPosition, false))
        { }

        /// <summary>
        /// Creates a new SplineSegment instance
        /// </summary>
        /// <param name="startPoint">the position of the start point</param>
        /// <param name="handlerPoint">the position of the handler point which is never crossed</param>
        /// <param name="endPoint">the position of the end point</param>
        public SplineSegment(SplinePoint startPoint, SplinePoint handlerPoint, SplinePoint endPoint)
        {
            this.startPoint = startPoint;
            this.endPoint = endPoint;
            this.handlerPoint = handlerPoint;

            AproximationVertexCount = 6;
            FitBounds();
        }

        /// <summary>
        /// Gets the enumeration type which is mapped to this kind of object
        /// </summary>
        /// <returns>the geometry typee of this object</returns>
        public override GeometryType GetGeometryType()
        {
            return GeometryType.SplineSegment;
        }


        internal override float GetDistanceToEdge(ref Vector2 point)
        {
            return Vector2.Distance(point, GetNearestPointOnEdge(point));
        }

        internal override Vector2 GetNearestPointOnEdge(ref Vector2 point)
        {
            return GetPositionFromT(GetEdgePathValueFromPoint(ref point));
        }

        internal override Vector2 GetPositionFromT(float t)
        {
            //return 2 * (handlerPoint.Position - StartPosition) + 2 * t * (EndPosition - 2 * handlerPoint.Position + StartPosition);

            Vector2 a = StartPosition + t * (handlerPoint.Position - StartPosition);
            Vector2 b = HandlerPoint.Position + t * (EndPosition - handlerPoint.Position);

            Vector2 result = a + t * (b - a);

            return result;
        }

        internal override float GetEdgePathValueFromPoint(ref Vector2 point)
        {
            return LineStripAproximation.GetEdgePathValueFromPoint(ref point);
        }

        internal override Vector2 GetTangent(ref Vector2 position)
        {
            Vector2 pos = LineStripAproximation.GetNearestPointOnEdge(ref position);
            return GetTangent(GetEdgePathValueFromPoint(ref pos));
        }

        /// <summary>
        /// gets a direction Vector which touches the given position witout crossing the outline at that position.
        /// </summary>
        /// <param name="t">the edge-path-value where the point of interest lies.</param>
        /// <returns>the tangent direction</returns>
        public Vector2 GetTangent(float t)
        {
            t %= 1;
            Vector2 a = handlerPoint.Position - StartPosition;
            Vector2 b = EndPosition - handlerPoint.Position;

            return Vector2.Normalize(Vector2.Lerp(a, b, t));
        }

        internal override Vector2 GetNormal(ref Vector2 position)
        {
            Vector2 tangent = GetTangent(ref position);
            return new Vector2(-tangent.Y, tangent.X);
        }

        /// <summary>
        /// gets a direction Vector which crosses the outline at the given position with 90°
        /// </summary>
        /// <param name="t">the edge-path-value where the point of interest lies.</param>
        /// <returns>the normal direction</returns>
        public Vector2 GetNormal(float t)
        {
            Vector2 tangent = GetTangent(t);
            return new Vector2(-tangent.Y, tangent.X);
        }

        private void OnChange()
        {
            MakeAproximation();
            FitBounds();
            CallOnChange();
        }

        private void MakeAproximation()
        {
            LineStripAproximation = this.ToLineStrip(aproximationVertexCount);
            _BorderLength = LineStripAproximation.BorderLength;
        }

        private void FitBounds()
        {
            float minX = Math.Min(StartPosition.X, Math.Min(handlerPoint.Position.X, EndPosition.X));
            float minY = Math.Min(StartPosition.Y, Math.Min(handlerPoint.Position.Y, EndPosition.Y));

            float maxX = Math.Max(StartPosition.X, Math.Max(handlerPoint.Position.X, EndPosition.X));
            float maxY = Math.Max(StartPosition.Y, Math.Max(handlerPoint.Position.Y, EndPosition.Y));

            _Transform.SetDimension(maxX - minX, maxY - minY);

        }

        /// <summary>
        /// Clones the SplineSegment
        /// </summary>
        /// <returns>a clone this SplineSegment</returns>
        public override object Clone()
        {
            SplineSegment clone = new SplineSegment(startPoint, endPoint, handlerPoint);
            clone.AproximationVertexCount = AproximationVertexCount;

            return clone;
        }

    }
}
